/**
 * Global
 * @type Global Instance
 * @description this variable is used for global static storing
 */
var Global = {
    damageInfinity: 999
};

/**
 * Vector-----------------------------------------------------------------------
 * @type Class
 */
var Vector2 = function(x, y) { 
    this.x = x; this.y = y; 
};
Vector2.prototype={
    reset:function(x,y){ 
        return new Vector2(x,y);
    },
    add:function(v){ 
        return new Vector2(this.x+v.x,this.y+v.y);
    },
    subtract:function(v){
        return new Vector2(this.x-v.x,this.y-v.y);
    },
    multiply:function(f){
        return new Vector2(this.x*f,this.y*f);
    },
    negate:function(){
        return new Vector2(-this.x,-this.y);
    },
    sqrLength:function(){
        return this.x * this.x + this.y * this.y;
    },
    copy : function() { 
        return new Vector2(this.x, this.y); 
    },
    dot :function(v){
        return this.x * v.x + this.y * v.y; 
    },
    length:function(){
        return Math.sqrt(this.x*this.x+this.y*this.y);
    },
    divide : function(f) { 
        var invf = 1/f; return new Vector2(this.x * invf, this.y * invf); 
    },
    normalize : function() { 
        var inv = 1/this.length(); return new Vector2(this.x * inv, this.y * inv); 
    }
};
Vector2.zero = new Vector2(0, 0);

/**
 * Normal rectangle
 * @type Class
 */
var Rectangle = function(x1, x2, y1, y2){
    this.x1 = x1;
    this.x2 = x2;
    this.y1 = y1;
    this.y2 = y2;
}

/**
 * Media manager----------------------------------------------------------------
 * @description Now only support Audio
 * @type Global Instance
 */
var MediaManager = {
    repository: {
	/*
	"fire": "music/enemy3U.wav"
	*/
    },
    loadMedia: function(repo){//Trans source url into Media object
	this.repository = repo;
    },
    play: function(name){
	var tmpMedia = document.createElement("audio");
	tmpMedia.src = this.repository[name];
	tmpMedia.play();
	delete tmpMedia;
    }
}

/**
 * Image manager----------------------------------------------------------------
 * @type Global Instance
 */
var ImageManager = {
    total: 0,
    repository: {
	/*
	"player": {
	    "up": "images/enemy3U.gif",
	    "down": "images/enemy3D.gif",
	    "left": "images/enemy3L.gif",
	    "right": "images/enemy3R.gif"
	}
	*/
    },
    loadImage: function(repo){//Trans source url into Image object
	this.repository = repo;
	//count total(here use a single loop to avoid any problem)
	for(var cat in this.repository){
	    for(var source in this.repository[cat]){
		this.total++;
	    }
	}
	//load image
	for(var cat in this.repository){
	    for(var source in this.repository[cat]){
		//init image
		var tmpImage = new Image();
		tmpImage.src = this.repository[cat][source];
		this.repository[cat][source] = tmpImage;
		var $this = this;
		tmpImage.onload = function(){
		    //increase loaded
		    $this.loadedNumber++;
		}
	    }
	}
    },
    loadedNumber: 0,
    isLoadedAll: function(){
	return this.loadedNumber >= this.total;
    }
}

/**
 * DisplayObject, just an instance, will share some tools for all object
 */
var DisplayObject = {
    position: new Vector2(0, 0),
    width: 0,
    height: 0,
    toRectangle: function(){
	return new Rectangle(this.position.x,
			     this.position.x + this.width,
			     this.position.y,
			     this.position.y + this.height);
    }
}

/**
 * Sprite-----------------------------------------------------------------------
 * @type Base Class
 */
var Sprite = function(x, y){
    this.position = new Vector2(x, y);
    
    this.speed = new Vector2(5, 5);
    this.hp = this.maxHp = 1;//Health Point
    
    this.width = 0;
    this.height = 0;
    this.isLive = true;
    
    //save current stage's reference
    this.stage = null;
    
    //[Implement]
    this._animations;
    this._currentAnimation;
    this._frames;//point to currentAnimation's frames
    this._currentFrame = 0;
}
Sprite.prototype = DisplayObject;
//[Abstract]
Sprite.prototype.setCurrentAnimation = function(animation, currentFrame){
    this._currentAnimation = animation;
    this._frames = this._animations[this._currentAnimation];
    //also update current frame
    if(currentFrame != undefined) this._currentFrame = currentFrame;
}
Sprite.prototype.getCurrentAnimation = function(animation){
    return this._currentAnimation;
}
Sprite.prototype.setCurrentFrame = function(index){
    this._currentFrame = index;
}
Sprite.prototype.getCurrentFrame = function(){
    return this._currentFrame;
}
Sprite.prototype.isPositionIn = function(position){
    return (position.x >= this.position.x &&
	    position.y >= this.position.y &&
	    position.x <= this.position.x + this.width &&
	    position.y <= this.position.y + this.height);
}
//[Abstract Functions]
Sprite.prototype.runAnimation = null;//forked when every "before this.draw()"
Sprite.prototype.onDestroy = null;
Sprite.prototype.onAdd = null;//forked when Stage addSprite(this)
//[Default]
Sprite.prototype.die = function(){//Let this die
    this.isLive = false;
}
Sprite.prototype.decreaseHP = function(damage){
    damage = damage || 1;
    this.hp -= damage;
    if(this.hp <= 0 && typeof(this.onDestroy) == "function"){
	this.onDestroy();
    }
}
Sprite.prototype.increaseHP = function(blood){
    blood = blood || 1;
    this.hp += blood;
    if(this.hp > this.maxHp) this.hp = this.maxHp;
}
//[Default]
Sprite.prototype.draw = function(context){
    //if(this instanceof EnemyTank) console.log(this._currentFrame);
    if(this._frames && this._frames[this._currentFrame] instanceof Image){
	//load children's draw first
	if(typeof(this.subDraw) == "function") this.subDraw(context);
	//just draw current frame
	context.save();
	context.drawImage(this._frames[this._currentFrame], this.position.x, this.position.y, this.width, this.height);
	context.restore();
    }
}
//[Need Implemented]draw before parent's draw
Sprite.prototype.subDraw = null;
//Forked when collided by other sprite
Sprite.prototype.onCollided = function(sponsor){}

/**
 * Stage------------------------------------------------------------------------
 * @type Class
 */
var Stage = function(canvas){
    this.canvas = canvas;
    this.context = this.canvas.getContext('2d');
    this.width = this.canvas.width;
    this.height = this.canvas.height;
    
    //buffer
    this.bufferCanvas = document.createElement('canvas');
    this.bufferCanvas.setAttribute('width', this.width);
    this.bufferCanvas.setAttribute('height', this.height);
    this.bufferContext = this.bufferCanvas.getContext('2d');
    
    this._fps = 0;
    this._stepTime = 0;
    
    this._sprites = [];
    
    this._timer = null;
    
    this._isStop = false;
    
    this._fpsShower = null;
    
    this._generator = new Generator(this);
}

Stage.prototype = DisplayObject;
Stage.prototype.init = function(fps, fpsShower){
    fps = fps || 30;//By default
    this.setFPS(fps);
    this._fpsShower = fpsShower || null;
    this.bufferContext.globalCompositeOperation = 'source-over';
}
Stage.prototype.setFPS = function(fps){
    this._fps = fps;
    this._stepTime = Math.floor(1000 / this._fps);
}
Stage.prototype.addSprite = function(sprite){
    if(sprite instanceof Array){
	for(var i = 0; i < sprite.length; i++){
	    this.addSprite(sprite[i]);
	}
    }else if(sprite instanceof Sprite){
	//give reference
	sprite.stage = this;
	//join
	this._sprites.push(sprite);
	//if has private init function, here will call
	if(typeof(sprite.onAdd) == "function") sprite.onAdd();
    }
}
Stage.prototype.start = function(){
    //MediaManager.play("start");
    this.init.apply(this, arguments);
    this.render();//just start
    /*[1]Way of running
    this._timer = setTimeout((function(stage){
	return function(){ stage.render() };
    })(this), this._stepTime);
    */
    //when the music stopped, we will let it start
    /*
    setTimeout((function(stage){
	return function(){stage.render();}
    })(this), 4000);
    */
}
Stage.prototype.clear = function(context, fillStyle){
    context.save();
    if(fillStyle){
	context.fillStyle = fillStyle;
	context.fillRect(0, 0, this.width, this.height);
    }else{
	context.clearRect(0, 0, this.width, this.height);
    }
    context.restore();
}
Stage.prototype.render = function(){
    if(this._isStop) return;
    
    //show fps
    this.showFPS();
    //clear & fill with bg
    this.clear(this.bufferContext, 'black');
    //Generator Manager runs here
    this._generator.run();
    //load every sprite's logic
    for(var i = 0; i < this._sprites.length; i++){
	if(this._sprites[i] instanceof Sprite){
	    /*
	    //take care of every Health Point
	    if(this._sprites[i].hp <= 0){
		if(typeof(this._sprites[i].onDestroy) == "function") this._sprites[i].onDestroy();
	    }
	    */
	    //remove dead children
	    if(this._sprites[i].isLive === false){
		//call generator's retrieve
		this._generator.retrieve(this._sprites[i]);
		//remove reference
		delete this._sprites[i];
		continue;
	    }
	    //run children's logic
	    if(typeof(this._sprites[i].runAnimation) == "function") this._sprites[i].runAnimation();
	    //draw children
	    this._sprites[i].draw(this.bufferContext);
	}
    }
    //draw on main canvas
    this.clear(this.context);
    this.context.drawImage(this.bufferCanvas, 0, 0);
    
    //new way of refresh (Use setTimeout instead of setInterval)
    this._timer = setTimeout((function(stage){
	return function(){ stage.render() };
    })(this), this._stepTime);
}
Stage.prototype.stop = function(){
    this._isStop = true;
    clearInterval(this._timer);
}
Stage.prototype.play = function(){
    if(this._isStop){
	this._isStop = false;
	this.render();
    }
}
Stage.prototype.getSpriteInPosition = function(position){
    //looping to find
    for(var i = 0; i < this._sprites.length; i++){
	if(this._sprites[i].isPositionIn(position)){
	    return this._sprites[i];
	}
    }
    return null;
}
//Get composite objects by given object(the second sprite is passed to exclude any sprite)
//(!!!NOTE!!! Here may need to be optimized)
Stage.prototype.getCompositeSprites = function(rectangle, sprite){
    var compositeSprites = [];
    for(var i = 0; i < this._sprites.length; i++){
	if(this._sprites[i] instanceof Sprite){
	    if(this._sprites[i] !== sprite &&
	       this.checkRectComposite(this._sprites[i].toRectangle(), rectangle)){
		compositeSprites.push(this._sprites[i]);
	    }
	}
    }
    return compositeSprites;
}
//"Composite Test" for rectangle
Stage.prototype.checkRectComposite = function(a, b){
    //check
    if(b.x1 >= a.x2 || b.x2 <= a.x1 || b.y1 >= a.y2 || b.y2 <= a.y1){
	return false;
    }
    return true;
}
//check if "rect" is out of "container", both params are Rectangle
Stage.prototype.checkRectOut = function(container, rect){
    return !(rect.x1 >= container.x1 &&
	    rect.x2 <= container.x2 &&
	    rect.y1 >= container.y1 &&
	    rect.y2 <= container.y2);
}
Stage.prototype.showFPS = function(){
    if(this._fpsShower){
	this._fpsShower.innerText = "FPS:" + this._fps + "; StepTime:" + this._stepTime;
    }
}
Stage.prototype.drawGameOver = function(){
    var title = "Game Over !";
    this.context.save();
    this.context.lineWidth = 2;
    this.context.font = "italic 100px serif";
    var metrics = this.context.measureText(title);
    var x = Math.floor((this.width - metrics.width)/2);
    var y = Math.floor(this.height/2);
    this.context.fillStyle = "rgba(255, 0, 0, 1)";
    this.context.fillText(title, x, y);
    this.context.strokeStyle = "rgba(255, 255, 255, 1)";
    this.context.strokeText(title, x, y);
    this.context.restore();
}
Stage.prototype.gameOver = function(){
    this.stop();
    this.drawGameOver();
    //TODO
}
/**
 * Generators-------------------------------------------------------------------
 * @type Class
 * @description 
 */
var Generator = function(stage){
    this.stage = stage;
    this._material = {
	"star": {
	    "generated": 0,
	    "maxQuantity": 3,
	    "class": Star
	},
	"peach": {
	    "generated": 0,
	    "maxQuantity": 3,
	    "class": Peach
	}
	//TODO
    }
}
Generator.prototype.run = function(){
    for(var type in this._material){
	var mat = this._material[type];
	if(mat["generated"] < mat["maxQuantity"]){
	    //generate one in random
	    var rand = Math.floor(Math.random() * 1000);
	    if(rand == 1){
		var x = Math.floor(Math.random() * (this.stage.width - Material.width));
		var y = Math.floor(Math.random() * (this.stage.height - Material.height));
		this.stage.addSprite(new mat["class"](x, y));
		mat["generated"]++;
	    }
	}
    }
}
//Forked when any sprite dead
Generator.prototype.retrieve = function(sprite){
    for(var type in this._material){
	var mat = this._material[type];
	if(sprite instanceof mat["class"]){
	    mat["generated"]--;
	    break;
	}
    }
}
